package tcln.gxtx.client.widget.grid.editor;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import tcln.gxtx.client.utils.CellEditorPredicate;
import tcln.gxtx.client.utils.assertion.GwtAssert;

import com.extjs.gxt.ui.client.data.ModelData;
import com.extjs.gxt.ui.client.event.BaseEvent;
import com.extjs.gxt.ui.client.event.EventType;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.widget.Editor;
import com.extjs.gxt.ui.client.widget.form.HiddenField;
import com.extjs.gxt.ui.client.widget.grid.CellEditor;

/**
 * @author Truong Chau Lien Ngoc (lienngoc@gmail.com)
 * 
 */
public class ContextualCellEditor extends CellEditor {
    private static final CellEditorPredicate DEFAULT_PREDICATE = new AlwaysTruePredicate();

    private Map<CellEditorPredicate, CellEditor> innerEditorMap;

    /**
     * Constructs this cell editor without editable field.
     */
    public ContextualCellEditor() {
        super(new HiddenField<String>());
        innerEditorMap = new HashMap<CellEditorPredicate, CellEditor>();
    }

    /**
     * Adds new component editor.
     * <p>
     * <b>Caution</b>: The order of this method's calls reflects the order of searching for correct editor via
     * {@link CellEditorPredicate}.
     * 
     * @param editor
     * @param predicate
     *            Predicate for lookup this editor by context.
     */
    public final void addEditor(CellEditor editor, CellEditorPredicate predicate) {
        GwtAssert.ARGUMENTS.notNull(predicate, "Sample model data must NOT be null");
        if (editor != null) {
            innerEditorMap.put(predicate, editor);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addListener(EventType eventType, Listener<? extends BaseEvent> listener) {
        super.addListener(eventType, listener);

        // add the same listener to all inner editors
        for (Editor innerEditor : innerEditorMap.values()) {
            innerEditor.addListener(eventType, listener);
        }
    }

    /**
     * Adds new component editor.
     * <p>
     * <b>Caution</b>: Because the order of this method's calls reflects the order of searching for correct editor via
     * {@link CellEditorPredicate}, it's highly recommended that this method be the last editor. <i>Putting this default
     * editor at first position causes this editor always be the chosen one</i>.
     * 
     * @param editor
     */
    public final void addDefaultEditor(CellEditor editor) {
        addEditor(editor, DEFAULT_PREDICATE);
    }

    /**
     * Gets an appropriate editor for the given context information (model, property and editing cell).
     * 
     * @param model
     * @param property
     * @param row
     * @param col
     * @return A component cell editor or <code>null</code> if nothing matches with given context.
     */
    public CellEditor getEditor(ModelData model, String property, int row, int col) {
        for (Entry<CellEditorPredicate, CellEditor> entry : innerEditorMap.entrySet()) {
            if (entry.getKey().evaluate(model, property, row, col)) {
                return entry.getValue();
            }
        }
        return null;
    }

    private static class AlwaysTruePredicate implements CellEditorPredicate {
        @Override
        public boolean evaluate(ModelData model, String property, int rowIndex, int colIndex) {
            return true;
        }
    }
}
